﻿//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Microsoft Public License.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace AstoriaOverAstoria
{
    using System;
    using System.Diagnostics;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;

    /// <summary>Implementation of <see cref="IQueryProvider"/> which turns server generated expressions
    /// into expressions accepted by the client library.</summary>
    internal class AstoriaOverAstoriaQueryProvider : IQueryProvider
    {
        /// <summary>The underlying query provider from the client LINQ.</summary>
        private IQueryProvider sourceQueryProvider;

        /// <summary>The context on which the query is about to run (metadata and such).</summary>
        private AstoriaOverAstoriaContext context;

        /// <summary>Resource annotations for the expressions processed by the provider.</summary>
        private ExpressionResourceAnnotations annotations;

        /// <summary>Creates new query provider.</summary>
        /// <param name="context">The context on which the query is to be executed (metadata and such).</param>
        /// <param name="sourceQueryProvider">The underlying client LINQ query provider.</param>
        internal AstoriaOverAstoriaQueryProvider(AstoriaOverAstoriaContext context, IQueryProvider sourceQueryProvider)
        {
            Debug.Assert(context != null, "context != null");
            Debug.Assert(sourceQueryProvider != null, "sourceQueryProvider != null");
            this.context = context;
            this.sourceQueryProvider = sourceQueryProvider;
            this.annotations = new ExpressionResourceAnnotations();
        }

        /// <summary>The context on which the queries will execute.</summary>
        internal AstoriaOverAstoriaContext Context
        {
            get { return this.context; }
        }

        /// <summary>Annotations for the expressions.</summary>
        internal ExpressionResourceAnnotations ExpressionAnnotations
        {
            get { return this.annotations; }
        }

        /// <summary>Creates a new query.</summary>
        /// <typeparam name="TElement">The type of the query results.</typeparam>
        /// <param name="expression">The expression tree to create the query for.</param>
        /// <returns>The newly created query.</returns>
        IQueryable<TElement> IQueryProvider.CreateQuery<TElement>(Expression expression)
        {
            return new AstoriaOverAstoriaQuery<TElement>(this, expression);
        }

        /// <summary>Creates a new query.</summary>
        /// <param name="expression">The expression tree to create the query for.</param>
        /// <returns>The newly created query.</returns>
        IQueryable IQueryProvider.CreateQuery(Expression expression)
        {
            Type et = TypeSystem.GetIEnumerableElementType(expression.Type);
            Type qt = typeof(AstoriaOverAstoriaQuery<>).MakeGenericType(et);
            object[] args = new object[] { this, expression };

            ConstructorInfo ci = qt.GetConstructor(
                BindingFlags.NonPublic | BindingFlags.Instance,
                null,
                new Type[] { typeof(AstoriaOverAstoriaQueryProvider), typeof(Expression) },
                null);

            return (IQueryable)ci.Invoke(args);
        }

        /// <summary>Executes the specified expression and returns a single result.</summary>
        /// <typeparam name="TResult">The type of the result.</typeparam>
        /// <param name="expression">The expression to run.</param>
        /// <returns>The single result of the query.</returns>
        TResult IQueryProvider.Execute<TResult>(Expression expression)
        {
            return (TResult)((IQueryProvider)this).Execute(expression);
        }

        /// <summary>Executes the specified expression and returns a single result.</summary>
        /// <param name="expression">The expression to run.</param>
        /// <returns>The single result of the query.</returns>
        object IQueryProvider.Execute(Expression expression)
        {
            ExpressionConversionResult conversionResult = this.ConvertExpression(expression);
            object result = this.sourceQueryProvider.Execute(conversionResult.Expression);
            if (conversionResult.QueryResultsProcessor != null)
            {
                result = conversionResult.QueryResultsProcessor.ProcessSingleResult(result, this.Context);
            }

            return result;
        }

        /// <summary>Creates a new query on the underlying client LINQ provider.</summary>
        /// <param name="sourceQueryExpression">The expression tree to create the query for.</param>
        /// <returns>The new client LINQ query.</returns>
        internal IQueryable CreateSourceQuery(Expression sourceQueryExpression)
        {
            return this.sourceQueryProvider.CreateQuery(sourceQueryExpression);
        }

        /// <summary>Creates a new query on the underlying client LINQ provider.</summary>
        /// <typeparam name="TElement">The type of the results.</typeparam>
        /// <param name="sourceQueryExpression">The expression tree to create the query for.</param>
        /// <returns>The new client LINQ query.</returns>
        internal IQueryable<TElement> CreateSourceQuery<TElement>(Expression sourceQueryExpression)
        {
            return this.sourceQueryProvider.CreateQuery<TElement>(sourceQueryExpression);
        }

        /// <summary>Sets a resource type annotation for the specified expression.</summary>
        /// <param name="expression">The expression to annotate.</param>
        /// <param name="resourceType">The resource type mapping to use as the annotation value.</param>
        /// <remarks>This is used by the context to annotate the root of the query with the base resource type of the resource set.</remarks>
        internal void SetResourceTypeAnnotation(Expression expression, ResourceTypeMapping resourceType)
        {
            this.annotations.AnnotateResourceType(expression, resourceType);
        }

        /// <summary>Converts the specified <paramref name="expression"/> generated by the server into a new expression which can be ran against the client provider.</summary>
        /// <param name="expression">The expression generated by the server.</param>
        /// <returns>The <see cref="ExpressionConversionResult"/> which contains the expression suitable for client provider along with
        /// the query results processor which should be applied to the query results before returning to the server.</returns>
        internal ExpressionConversionResult ConvertExpression(Expression expression)
        {
            Trace.WriteLine("Before: " + expression.ToString());
            ExpressionConversionResult result = ExpressionConverter.Convert(expression, this.ExpressionAnnotations, this.Context.ContextMapping);
            Trace.WriteLine("After: " + result.Expression.ToString());
            return result;
        }
    }
}